//
//  NSObject+kvo.m
//  运行时自定义KVO
//
//  Created by Hello Cai on 2019/11/25.
//  Copyright © 2019 Hello Cai. All rights reserved.
//

#import "NSObject+kvo.h"
#import <objc/message.h>
#import <objc/runtime.h>
static const char *observerKey = "observerKey";

@implementation NSObject (kvo)

- (void)K_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    
    //1.创建一个类
    NSString *oldName = NSStringFromClass([self class]);
    NSString *newName = [@"HKVO_" stringByAppendingString:oldName];
    
    //1.1这个类继承谁
    Class MyClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
    //1.2注册类
    objc_registerClassPair(MyClass);
    
    //2.动态修改self的类型
    object_setClass(self, MyClass);
    
    //3.重写setName:  给子类添加setName:方法
    class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");
    
    //4.将observer（观察者）绑定到对象上
    objc_setAssociatedObject(self, observerKey, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

void setName(id self, SEL _cmd, NSString *newName){
    NSLog(@"++%@", newName);
    
    /* 以下这两句类似[super setName]，去父类方法列表里找，但调用主体（调用者）还是receiver,
        也就是结构体中第一个成员变量self。
        可以在父类Person的setName方法中断点用lldb打印"po self"，可发现打印结果为：
     <HKVO_Person: 0x600003326e00>
        作用是更新父类的name属性值（可写可不写）
     补充一点：和self = [super initWithFrame:]一样，
     super的意义只是到self的父类的方法列表里找initWithFrame:执行，但调用者本质是self
     */
    struct objc_super person = {self, class_getSuperclass([self class])};
    objc_msgSendSuper(&person, @selector(setName:), newName);
    
    // 拿出观察者
    id observer = objc_getAssociatedObject(self, observerKey);
    // 调用者为observer
    /*注意点：Xcode编译器无法识别出objc_msgSend是函数
     解决方式一：TARGETS -> Build Settings -> Enable Strict Checking of objc_msgSend Calls设置为NO
     解决方式二：类型强转
     ((void (*) (id, SEL, id, id, id, id))objc_msgSend)(observer, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self, @{NSKeyValueChangeNewKey:newName}, nil);
     */
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self, @{NSKeyValueChangeNewKey:newName}, nil);
}

@end
